package com.vr.cloudpicture.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vr.cloudpicture.common.PageResult;
import com.vr.cloudpicture.constant.UserConstant;
import com.vr.cloudpicture.enums.UserRoleEnum;
import com.vr.cloudpicture.exception.BusinessException;
import com.vr.cloudpicture.exception.ErrorCode;
import com.vr.cloudpicture.exception.ThrowUtils;
import com.vr.cloudpicture.manager.auth.StpKit;
import com.vr.cloudpicture.mapper.UserMapper;
import com.vr.cloudpicture.model.dto.space.SpaceAddRequest;
import com.vr.cloudpicture.model.dto.user.*;
import com.vr.cloudpicture.model.entity.User;
import com.vr.cloudpicture.model.vo.UserVO;
import com.vr.cloudpicture.service.ISpaceService;
import com.vr.cloudpicture.service.IUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author hzh
 * @description 针对表【user(用户)】的数据库操作Service实现
 * @createDate 2025-04-25 17:06:20
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private final UserMapper userMapper;
    private final RedissonClient redissonClient;
    @Lazy
    @Autowired
    private ISpaceService spaceService;
    @Lazy
    @Autowired
    private IUserService userService;

    @Override
    public Long userRegister(UserRegisterRequest userRegisterRequest) {
        // 校验
        ThrowUtils.throwIf(userRegisterRequest == null, ErrorCode.PARAMS_ERROR, "参数为空");
        ThrowUtils.throwIf(StrUtil.isBlank(userRegisterRequest.getUserAccount()), ErrorCode.PARAMS_ERROR, "用户账号不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(userRegisterRequest.getUserPassword()), ErrorCode.PARAMS_ERROR, "密码不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(userRegisterRequest.getCheckPassword()), ErrorCode.PARAMS_ERROR, "确认密码不能为空");
        ThrowUtils.throwIf(userRegisterRequest.getUserPassword().length() < 3, ErrorCode.PARAMS_ERROR, "密码长度不能小于3位");
        ThrowUtils.throwIf(!userRegisterRequest.getUserPassword().equals(userRegisterRequest.getCheckPassword()), ErrorCode.PARAMS_ERROR, "两次密码不一致");
        // 用户不存在时开始注册
        return userService.doRegister(userRegisterRequest);
    }

    @Override
    public Long doRegister(UserRegisterRequest userRegisterRequest) {
        // 查找用户
        Long count = userMapper.selectCount(
                new LambdaQueryWrapper<User>()
                        .eq(User::getUserAccount, userRegisterRequest.getUserAccount())
        );
        ThrowUtils.throwIf(count > 0, ErrorCode.PARAMS_ERROR, "账号已注册");
        User user = User.builder()
                .userAccount(userRegisterRequest.getUserAccount())
                .userPassword(BCrypt.hashpw(userRegisterRequest.getUserPassword()))
                .userName(userRegisterRequest.getUserAccount())
                .userRole(UserRoleEnum.USER.getValue())
                .editTime(LocalDateTime.now())
                .build();
        int result = userMapper.insert(user);
        ThrowUtils.throwIf(result <= 0, ErrorCode.OPERATION_ERROR, "注册用户失败");
        // 自动创建空间，就算失败也有手动创建兜底
        UserLogin userLogin = BeanUtil.toBean(user, UserLogin.class);
        spaceService.addSpace(new SpaceAddRequest(), userLogin);
        return user.getId();
    }

    @Override
    public String userLogin(UserLoginRequest userLoginRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(userLoginRequest == null, ErrorCode.PARAMS_ERROR, "参数为空");
        ThrowUtils.throwIf(StrUtil.isBlank(userLoginRequest.getUserAccount()), ErrorCode.PARAMS_ERROR, "用户账号不能为空");
        ThrowUtils.throwIf(StrUtil.isBlank(userLoginRequest.getUserPassword()), ErrorCode.PARAMS_ERROR, "密码不能为空");
        ThrowUtils.throwIf(userLoginRequest.getUserPassword().length() < 3, ErrorCode.PARAMS_ERROR, "密码错误");
        User user = userMapper.selectOne(
                new LambdaQueryWrapper<User>()
                        .eq(User::getUserAccount, userLoginRequest.getUserAccount()));
        // 用户不存
        if (user == null) {
            ThrowUtils.throwIf(true, ErrorCode.PARAMS_ERROR, "用户不存在");
        }
        // 校验密码
        boolean checkpw = BCrypt.checkpw(userLoginRequest.getUserPassword(), user.getUserPassword());
        if (!checkpw) {
            ThrowUtils.throwIf(true, ErrorCode.PARAMS_ERROR, "密码错误");
        }
        // 记录用户的登录态
        // 会通过前端自动传过来的JSESSIONID,对应的Session,再通过USER_LOGIN_STATE获取用户信息
        // 没有传JSESSIONID,则创建一个Session，并且将用户信息存入Session中，自动返回给前端一个JSESSIONID，前端自动存储到cookie里
        // Session默认会30销毁
        // 对user信息脱敏一下
        UserLogin userLogin = BeanUtil.copyProperties(user, UserLogin.class);
        request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, userLogin);
        // 登录态也记录到so-token里后面空间鉴权
        StpKit.SPACE.login(userLogin.getId());
        StpKit.SPACE.getSession().set(UserConstant.USER_LOGIN_STATE, userLogin);
        log.debug("用户登录成功");
        return "ok";
    }

    @Override
    public UserLogin getLoginUser(HttpServletRequest request) {
        // 获取当前用户
        UserLogin userLogin = (UserLogin) request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
        ThrowUtils.throwIf(userLogin == null || userLogin.getId() == null, ErrorCode.NOT_LOGIN_ERROR);
        // 再看一下数据库
        Long count = userMapper.selectCount(new LambdaQueryWrapper<User>().eq(User::getId, userLogin.getId()));
        ThrowUtils.throwIf(count <= 0, ErrorCode.NOT_LOGIN_ERROR);
        return userLogin;
    }

    @Override
    public Boolean userLogout(HttpServletRequest request) {
        HttpSession session = request.getSession();
        UserLogin userLogin = (UserLogin) session.getAttribute(UserConstant.USER_LOGIN_STATE);
        // 没有JSessionId,或者没有对应Session空间
        ThrowUtils.throwIf(userLogin == null, ErrorCode.OPERATION_ERROR, "未登录");
        // 移除登录态
        request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
        request.getSession().invalidate();
        StpKit.SPACE.logout(userLogin);
        StpKit.SPACE.getSession().delete(UserConstant.USER_LOGIN_STATE);
        return true;
    }

    @Override
    public PageResult<UserVO> pageQuery(UserQueryRequest userQueryRequest) {
        ThrowUtils.throwIf(userQueryRequest == null, ErrorCode.PARAMS_ERROR, "请求参数为空");
        // 开启分页
        Page<User> page = Page.of(userQueryRequest.getPageNum(), userQueryRequest.getPageSize());
        Page<User> userPage = userMapper.selectPage(page, new LambdaQueryWrapper<User>()
                        .eq(userQueryRequest.getId() != null, User::getId, userQueryRequest.getId())
                        .like(StrUtil.isNotBlank(userQueryRequest.getUserProfile()), User::getUserProfile, userQueryRequest.getUserProfile())
                        .likeRight(StrUtil.isNotBlank(userQueryRequest.getUserName()), User::getUserName, userQueryRequest.getUserName())
                        .likeRight(StrUtil.isNotBlank(userQueryRequest.getUserAccount()), User::getUserAccount, userQueryRequest.getUserAccount())
                        .eq(StrUtil.isNotBlank(userQueryRequest.getUserRole()), User::getUserRole, userQueryRequest.getUserRole())
                // TODO 后面扩展排序
        );
        List<UserVO> userVOList = BeanUtil.copyToList(userPage.getRecords(), UserVO.class);
        return new PageResult<UserVO>()
                .setTotal(userPage.getTotal())
                .setRecords(userVOList)
                .setPageNum(userPage.getCurrent())
                .setPageSize(userPage.getSize());
    }

    @Override
    public Boolean updateUser(UserUpdateRequest userUpdateRequest) {
        ThrowUtils.throwIf(userUpdateRequest == null, ErrorCode.PARAMS_ERROR, "参数为空");
        ThrowUtils.throwIf(userUpdateRequest.getId() == null, ErrorCode.PARAMS_ERROR, "id不能为空");
        User user = userMapper.selectById(userUpdateRequest.getId());
        ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "用户不存在,请刷新一下");
        BeanUtil.copyProperties(userUpdateRequest, user);
        return userMapper.updateById(user) > 0;
    }

    @Override
    public Boolean saveUser(UserAddRequest userAddRequest) {
        ThrowUtils.throwIf(userAddRequest == null, ErrorCode.PARAMS_ERROR, "参数为空");
        User user = BeanUtil.toBean(userAddRequest, User.class);
        return userMapper.insert(user) > 0;
    }

    private static final String VIP_CODE_FILE = "classpath:biz/vipCode.json";

    @Override
    @Transactional
    public Boolean exchangeVip(VipExchangeRequest vipExchangeRequest, UserLogin userLogin) {
        // 参数校验
        ThrowUtils.throwIf(vipExchangeRequest == null || StrUtil.isEmpty(vipExchangeRequest.getVipCode()), ErrorCode.PARAMS_ERROR);
        User user = userService.getById(userLogin.getId());
        ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "用户不存在");
        String vipCode = vipExchangeRequest.getVipCode();
        // 1. 读取JSON文件
        JSONArray codeArray = readVipCodeFile();

        // 用兑换码做为锁，避免重复兑换
        RLock lock = redissonClient.getLock(vipCode);
        try {
            // 2. 查找匹配的兑换码
            JSONObject targetCode = findCode(codeArray, vipExchangeRequest.getVipCode());
            ThrowUtils.throwIf(targetCode == null, ErrorCode.PARAMS_ERROR, "无效的兑换码");
            ThrowUtils.throwIf(targetCode.getBool("hasUsed"), ErrorCode.OPERATION_ERROR, "兑换码已被使用");
            lock.lock();
            // 3.更新数据库
            // 判断用户是否已是会员，如果没有过期就续期
            LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(User::getId, userLogin.getId())
                    .set(User::getUserRole, UserRoleEnum.VIP.getValue());
            if (!UserRoleEnum.VIP.getValue().equals(user.getUserRole())) {
                updateWrapper.set(User::getVipExpireTime, LocalDateTime.now().plusYears(1));
            } else {
                LocalDateTime now = LocalDateTime.now();
                // 过期了,当前时间一年后
                updateWrapper.set(now.isAfter(user.getVipExpireTime()), User::getVipExpireTime,now.plusYears(1));
                // 没过期，在原来的时间上再续上一年
                updateWrapper.set(now.isBefore(user.getVipExpireTime()), User::getVipExpireTime,user.getVipExpireTime().plusYears(1));
            }
            // 更新
            boolean update = userService.update(updateWrapper);
            ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "会员兑换码兑换失败");

            // 4. 标记为已使用
            targetCode.set("hasUsed", true);
            // 5. 保存更新后的文件
            saveVipCodeFile(codeArray);
            return true;
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "会员兑换码兑换失败");
        } finally {
            // 不管成功还是失败都释放锁
            lock.unlock();
        }

    }

    private JSONArray readVipCodeFile() {
        try {
            File file = ResourceUtils.getFile(VIP_CODE_FILE);
            String jsonStr = FileUtil.readString(file, StandardCharsets.UTF_8);
            return JSONUtil.parseArray(jsonStr);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("兑换码文件不存在", e);
        }
    }

    private JSONObject findCode(JSONArray codeArray, String code) {
        List<JSONObject> codes = codeArray.stream()
                .map(obj -> (JSONObject) obj)
                .collect(Collectors.toList());

        return codes.stream()
                .filter(c -> c.getStr("code").equals(code))
                .findFirst()
                .orElse(null);
    }

    private void saveVipCodeFile(JSONArray codeArray) {
        try {
            File file = ResourceUtils.getFile(VIP_CODE_FILE);
            FileUtil.writeString(codeArray.toStringPretty(), file, StandardCharsets.UTF_8);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("兑换码文件保存失败", e);
        }
    }

    @Override
    public boolean isAdmin(UserLogin user) {
        return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
    }

    @Override
    public UserVO getUserVOById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            return null;
        }
        return BeanUtil.toBean(user, UserVO.class);
    }
}




